package com.example.myapplication2;

import java.lang.*;
import java.net.InetSocketAddress;
import java.util.*;
import java.nio.channels.*;
import java.nio.*;

class sockMapData {
    int num;
    byte[] id;
    ArrayList<Long> askList;
    ArrayList<Long> dataList;
    long createTime;

    sockMapData(int port, byte[] i, ArrayList<Long> al,
                ArrayList<Long> dl, long ct) {
        num = port;
        id = i;
        askList = al;
        dataList = dl;
        createTime = ct;
    }
}

class aliveSockV {
    DatagramChannel so;
    int port;

    aliveSockV(DatagramChannel s, int p) {
        so = s;
        port = p;
    }
}

class dam {
    public static ArrayList<String> damL = new ArrayList<String>();
    public static ArrayList<byte[]> damLB = new ArrayList<byte[]>();
    public static String s = defaultChi.defaultChi;
    public static Map<Integer, String> m = new HashMap<Integer, String>();
    public static Map<String, Integer> m2 = new HashMap<String, Integer>();
    static String b2chi(byte[] ss, String si) {
        myBuf w = new myBuf(ss);
        byte[] b = new byte[3 - ss.length % 3];
        w.writeArr(b);
        ss = w.get();
        int co = 0;
        StringBuilder builder = new StringBuilder();
        for (int lo = 0; lo < ss.length; lo++) {
            if (lo % 600 == 0)
                builder.append(si);
            if (lo % 3 == 0) {
                co = helpFunc.b2i(ss[lo]) * 16;
            } else if (lo % 3 == 1) {
                co += helpFunc.b2i(ss[lo]) / 16;
                builder.append(dam.m.get(co));
                co = (helpFunc.b2i(ss[lo]) % 16) * 256;
            } else if (lo % 3 == 2) {
                co += helpFunc.b2i(ss[lo]);
                builder.append(dam.m.get(co));
            }
        }
        return builder.toString();
    }

    public static void ini() {
        for (int lo = 0; lo < s.length(); lo++) {
            m.put(lo, s.substring(lo, lo + 1));
            m2.put(s.substring(lo, lo + 1), lo);
        }

        for (String st : defaultChi.damList)
            damL.add(st);

        int co = 0;
        for (String st : damL) {
            myBuf w = new myBuf();
            if (st.length() % 2 == 1)
                st += m.get(0);
            for (int i = 0; i < st.length(); i++) {
                if (!m2.containsKey(st.substring(i, i + 1)))
                    helpFunc.print(st.substring(i, i + 1));
                if (i % 2 == 0) {
                    int a = m2.get(st.substring(i, i + 1)) / 16;
                    w.writeByte(a);
                    co = m2.get(st.substring(i, i + 1)) % 16;
                } else {
                    co = co * 16 + m2.get(st.substring(i, i + 1)) / 256;
                    w.writeByte(co);
                    int a = m2.get(st.substring(i, i + 1)) % 256;
                    w.writeByte(a);
                }
            }
            damLB.add(w.get());
        }
    }
}

class msgT {
    byte[] msg;
    long time;
    boolean send;

    msgT(byte[] m, long t, boolean s) {
        msg = m;
        time = t;
        send = s;
    }
}

public class cli extends base {
    LinkedList<Integer> deadSock;
    LinkedList<aliveSockV> aliveSock = new LinkedList<aliveSockV>();
    Map<DatagramChannel, sockMapData> sockMap = new HashMap<DatagramChannel, sockMapData>();
    long staTime = System.currentTimeMillis();
    int staGot = 0, staOut = 0;
    int udpCo = 0;
    long lastCheckTimeout = 0;
    int newSockThisSecond = 0;
    int recvTime = 0;
    int getNumPerPack = helpFunc.con_getNum;
    long peerMaxPos = 0;
    Map<Long, sendMapV> recvMap = new HashMap<Long, sendMapV>();
    int rRaw = 0, wRaw = 0;
    int timeoutTime = helpFunc.timeoutTime;
    int peerMaxGotLen = 0, peerMaxSendLen = 0;
    int nullSend = 0;
    int staSend = 0;
    int newSockLimit = helpFunc.maxNewSockPerSecond;
    ServerSocketChannel controlSock;
    int controlPort;
    logClass myLog;
    int serverTotalSendLen = helpFunc.serverSenLen;
    int serverOneLen = helpFunc.serverPackLen;
    int totalSend = 0;
    LinkedList<byte[]> sendL = new LinkedList<byte[]>();
    LinkedList<byte[]> recvL = new LinkedList<byte[]>();
    ArrayList<msgT> msgCache = new ArrayList<msgT>();

    cli(LinkedList<Integer> ports, logClass lo) throws Exception {
        super(helpFunc.cliPackLen);
        deadSock = ports;
        myLog = lo;
        iniControlSock();
        dam.ini();
    }

    void iniControlSock() throws Exception {
        controlSock = ServerSocketChannel.open();
        controlPort = helpFunc.bindAnyPort(controlSock);
        controlSock.configureBlocking(false);
    }

    byte[] getId() {
        udpCo++;
        if (udpCo == 60000)
            udpCo = 0;
        myBuf w = new myBuf();
        w.writeWord(udpCo);
        if (helpFunc.hasDam) {
            int st = udpCo % dam.damL.size();
            w.writeArr(dam.damLB.get(st));
        }
        return w.get();
    }

    void dealSta(byte[] ss) {
        ArrayList<Long> l = new ArrayList<Long>();
        myBuf w = new myBuf(ss);
        long mi = w.readQ();
        if (mi > peerPos) {
            int le = 0;
            for (long i = peerPos; i < mi; i++) {
                le += sendMap.get(i).data.length;
                sendMap.remove(i);
            }
            peerPos = mi;
            tellAddBuf(le);
        }
        long ma = w.readQ();
        byte[] d = w.get();
        int co = -1;
        byte b = 0;
        int pos = 0;
        for (long i = mi; i < ma + 1; i++) {
            co++;
            if (co == 0) {
                b = d[pos];
                pos++;
                if ((b & 128) != 0)
                    l.add(i);
                b <<= 1;
            } else {
                if ((b & 128) != 0)
                    l.add(i);
                b <<= 1;
                if (co == 7)
                    co = -1;
            }
        }
        for (long one : l) {
            if (sendMap.containsKey(one))
                sendMap.get(one).got = true;
        }
    }


    void dealRecvPack(DatagramChannel channel) throws Exception {
        int num = sockMap.get(channel).num;
        byte[] id = sockMap.get(channel).id;
        byte[] j = new byte[0];
        try {
            j = helpFunc.udpRead(channel);
        } catch (Exception e) {
            addToDeadSock(channel);
            return;
        }
        myBuf w = new myBuf(j);
        int lll = w.readByte();
        byte[] xx = w.readArr(lll);
        if (!helpFunc.arrayEqu(xx, id)) {
            addToDeadSock(channel);
            return;
        }
        ArrayList<Long> askList = sockMap.get(channel).askList;
        ArrayList<Long> dataList = sockMap.get(channel).dataList;
        if (dataList.size() > 0 && sendMap.containsKey(dataList.get(0)))
            sendMap.get(dataList.get(0)).got = true;

        helpFunc.addMid(recvL, j, helpFunc.damLen);
        lastRecvTime = System.currentTimeMillis();
        msgCache.add(new msgT(j, lastRecvTime, false));
        staGot += 1;
        rRaw += j.length;
        if (j.length > maxGotLen)
            maxGotLen = j.length;
        long tt = System.currentTimeMillis() - sockMap.get(channel).createTime;
        if (tt > recvTime)
            recvTime = (int) tt;
        long ttt = w.readQ();
        if (ttt > peerMaxPos)
            peerMaxPos = ttt;
        int pmg = w.readWord();
        if (pmg > peerMaxGotLen)
            peerMaxGotLen = pmg;
        pmg = w.readWord();
        if (pmg > peerMaxSendLen)
            peerMaxSendLen = pmg;
        int sendNum = w.readByte();
        for (int i = 0; i < askList.size(); i++) {
            long nn = askList.get(i);
            byte[] st = new byte[0];
            if (i < sendNum) {
                int le = w.readWord();
                st = w.readArr(le);
            }
            if (!recvMap.containsKey(nn) || recvMap.get(nn).got)
                continue;
            if (i < sendNum && st.length > 0) {
                recvMap.get(nn).got = true;
                recvMap.get(nn).data = st;
                checkRecvMap();
            } else {
                recvMap.get(nn).sendTime--;
            }
        }
        int hasSta = w.readByte();
        if (hasSta == 1) {
            int le = w.readWord();
            byte[] sta = w.readArr(le);
            dealSta(sta);
        }
        int addPackNum = w.readByte();
        for (int i = 0; i < addPackNum; i++) {
            long nn = w.readQ();
            int ll = w.readWord();
            byte[] st = w.readArr(ll);
            if (nn < recvPos)
                continue;
            sendMapV v = new sendMapV();
            v.got = true;
            v.data = st;
            recvMap.put(nn, v);
        }
        checkRecvMap();
        sockMap.remove(channel);
        aliveSockV v = new aliveSockV(channel, num);
        aliveSock.add(v);
    }

    void doPeriodWork() {
        if (System.currentTimeMillis() - lastRecvTime > 120 * 1000)
            System.exit(0);
        timeoutTime = recvTime + helpFunc.con_timeoutPlus;
        if (recvTime == 0 || recvTime > helpFunc.timeoutTime)
            timeoutTime = helpFunc.timeoutTime;
        if (staOut == 0)
            gotRate = 1;
        else
            gotRate = staGot / (float) (staGot + staOut);
        maxAskTime = calAskTime();
        String log = String.format("%3d %4d %4d %3d|%4d %4d|%5d %5d %5d %5d",
                sockMap.size(), staGot, staOut, recvTime / 10, (int) (rRaw / 1024), (int) (wNet / 1024),
                maxSenLen, peerMaxGotLen, peerMaxSendLen, maxGotLen);

        myLog.setLog(log);
        myLog.setLogChi(sendL, recvL);
        myLog.setLogChiT(msgCache);
        msgCache.clear();
        sendL.clear();
        recvL.clear();
        staGot = staOut = newSockThisSecond = recvTime = staSend = 0;
        rRaw = wRaw = rNet = wNet = 0;
        nullSend = totalSend = 0;
        peerMaxGotLen = peerMaxSendLen = maxGotLen = maxSenLen = 0;
    }

    void sendPacks() throws Exception {
        float passTime = (System.currentTimeMillis() - staTime) / (float) 1000;
        if (passTime > 1)
            passTime = 1;

        int n = (int) (passTime * newSockLimit - newSockThisSecond);
        if (n < 0)
            n = 0;
        if (n > deadSock.size())
            n = deadSock.size();
        n = aliveSock.size() + n;

        float minPackNum, maxSend;
        if (gotRate == 0)
            minPackNum = maxSend = 20000;
        else {
            minPackNum = passTime * (helpFunc.serverStaPerSecond / gotRate);
            maxSend = passTime * (helpFunc.con_maxPackRecv / gotRate);
        }
        int maxSend_int = (int) (maxSend - totalSend);
        if (n > maxSend_int)
            n = maxSend_int;
        int staSendNum = (int) (minPackNum - staSend);
        if (staSendNum < 0)
            staSendNum = 0;
        if (staSendNum > n)
            staSendNum = n;
        staSend += staSendNum;
        ArrayList<ArrayList<Long>> askList = getAskList(n, staSendNum);
        ArrayList<Long> dataList = getDataToSend(n);
        totalSend += n;
        for (int i = 0; i < n; i++) {
            ArrayList<Long> al = new ArrayList<Long>();
            ArrayList<Long> dl = new ArrayList<Long>();
            if (askList.size() > i)
                al = askList.get(i);
            if (dataList.size() > i)
                dl.add(dataList.get(i));
            int hasSta = 0;
            if (staSendNum > 0) {
                staSendNum -= 1;
                hasSta = 1;
            } else {
                hasSta = 0;
                if (al.size() == 0 && dl.size() == 0)
                    continue;
            }
            if (aliveSock.size() == 0) {
                int a = deadSock.pop();
                DatagramChannel s = DatagramChannel.open();
                s.configureBlocking(false);
                newSockThisSecond++;
                aliveSockV v = new aliveSockV(s, a);
                aliveSock.add(v);
            }
            packMsg_send(al, dl, hasSta);
            if (al.size() == 0 && dl.size() == 0)
                nullSend++;
        }
    }

    void dealControl() throws Exception {
        SocketChannel a = controlSock.accept();
        byte[] j = helpFunc.tcpRead(a);
        if (j.length != 2) {
            a.close();
            return;
        }
        myBuf w = new myBuf(j);
        newSockLimit = w.readWord();
        a.close();
        String st = "newSockLimit " + newSockLimit;
        helpFunc.print(st);
        myLog.setLog(st);
    }

    void work() throws Exception {
        Selector selector = Selector.open();
        controlSock.register(selector, SelectionKey.OP_ACCEPT);
        for (DatagramChannel channel : sockMap.keySet())
            channel.register(selector, SelectionKey.OP_READ);
        rig(selector);
        int ret = selector.select(calWaitTime());
        Iterator selectedKeys = selector.selectedKeys().iterator();
        while (selectedKeys.hasNext()) {
            SelectionKey key = (SelectionKey) selectedKeys.next();
            selectedKeys.remove();
            if (!key.isValid()) {
                continue;
            }
            SelectableChannel channel = key.channel();
            if (channel == controlSock && key.isAcceptable())
                dealControl();
            dealFd(channel, key);
            if (sockMap.containsKey(channel) && key.isReadable()) {
                dealRecvPack((DatagramChannel) channel);
            }
        }
        selector.close();
        reFreshSendMap();
        if (System.currentTimeMillis() - lastCheckTimeout > helpFunc.con_timeoutInternal) {
            lastCheckTimeout = System.currentTimeMillis();
            dealTimeout();
        }
        sendPacks();
        if (System.currentTimeMillis() - staTime > 1000) {
            staTime = System.currentTimeMillis();
            doPeriodWork();
        }

    }

    void checkRecvMap() {
        while (recvMap.containsKey(recvPos)
                && recvMap.get(recvPos).got) {
            wrietBuf.append(recvMap.get(recvPos).data);
            recvMap.remove(recvPos);
            recvPos++;
        }
    }

    void packMsg_send(ArrayList<Long> askList, ArrayList<Long> dataList, int hasSta)
            throws Exception {
        Collections.sort(askList);
        long tt = System.currentTimeMillis();
        aliveSockV v = aliveSock.pop();
        myBuf w = new myBuf(2000);
        w.writeArr(key);
        byte[] id = getId();
        w.writeByte(id.length);
        w.writeArr(id);
        w.writeQ(recvPos);
        w.writeFloat(gotRate);
        if (dataList.size() == 0)
            w.writeByte(0);
        else {
            w.writeByte(1);
            long dd = dataList.get(0);
            w.writeQ(dd);
            w.writeWord(sendMap.get(dd).data.length);
            w.writeArr(sendMap.get(dd).data);
        }
        int le = askList.size();
        w.writeByte(hasSta);
        w.writeByte(le);
        if (le != 0) {
            w.writeQ(askList.get(0));
            for (int i = 0; i < le - 1; i++) {
                w.writeDWord((int) (askList.get(i + 1) - askList.get(0)));
            }
        }
        sockMapData vv = new sockMapData(v.port, id, askList, dataList, tt);
        InetSocketAddress add = new InetSocketAddress(helpFunc.serverIp, v.port);
        byte[] aa = w.get();
        try {
            helpFunc.udpWrite(v.so, aa, add);
        } catch (Exception e) {
        }
        helpFunc.addMid(sendL, aa, helpFunc.damLen);
        msgCache.add(new msgT(aa, tt, true));
        sockMap.put(v.so, vv);
        wRaw += aa.length;
        if (aa.length > maxSenLen)
            maxSenLen = aa.length;
    }

    ArrayList<ArrayList<Long>> getAskList(int n, int staPackNum) {
        ArrayList<ArrayList<Long>> l = new ArrayList<ArrayList<Long>>();
        if (n == 0)
            return l;
        int staLen = (int) (17 + (sendPos - peerPos) / 8);
        int sendNum = (serverTotalSendLen - 20 - (helpFunc.bool2int(helpFunc.hasDam) * helpFunc.damMaxLen) - staLen) / (serverOneLen + 2);
        if (sendNum <= 0)
            sendNum = 0;

        ArrayList<Long> tempList = new ArrayList<Long>();
        int thisLoopSendTime = 0;
        while (true) {
            thisLoopSendTime++;
            if (thisLoopSendTime > maxAskTime) {
                if (tempList.size() != 0 && l.size() < n)
                    l.add(tempList);
                return l;
            }
            for (long i = recvPos; i < peerMaxPos; i++) {
                if (staPackNum > 0 && tempList.size() >= sendNum) {
                    staPackNum -= 1;
                    l.add(tempList);
                    tempList = new ArrayList<Long>();
                    if (l.size() == n)
                        return l;
                }
                if (!recvMap.containsKey(i))
                    recvMap.put(i, new sendMapV());
                if (!recvMap.get(i).got && recvMap.get(i).sendTime < thisLoopSendTime) {
                    if (tempList.contains(i)) {
                        l.add(tempList);
                        tempList = new ArrayList<Long>();
                        if (l.size() == n)
                            return l;
                    }
                    recvMap.get(i).sendTime++;
                    tempList.add(i);
                    if (tempList.size() == getNumPerPack) {
                        l.add(tempList);
                        tempList = new ArrayList<Long>();
                        if (l.size() == n)
                            return l;
                    }
                }
            }
        }
    }

    ArrayList<Long> getDataToSend(int n) {
        ArrayList<Long> l = new ArrayList<Long>();
        if (n == 0)
            return l;
        int thisLoopSendTime = 0;
        while (true) {
            thisLoopSendTime++;
            if (thisLoopSendTime > maxAskTime)
                return l;
            for (long i = peerPos; i < sendPos; i++) {
                if (!sendMap.get(i).got && sendMap.get(i).sendTime < thisLoopSendTime) {
                    sendMap.get(i).sendTime++;
                    l.add(i);
                    if (l.size() == n)
                        return l;
                }
            }
        }
    }

    void addToDeadSock(DatagramChannel channel) throws Exception {
        int num = sockMap.get(channel).num;
        ArrayList<Long> askList = sockMap.get(channel).askList;
        ArrayList<Long> dataList = sockMap.get(channel).dataList;
        for (long i : askList) {
            if (recvMap.containsKey(i) && !recvMap.get(i).got)
                recvMap.get(i).sendTime--;
        }
        if (dataList.size() > 0) {
            long dd = dataList.get(0);
            if (sendMap.containsKey(dd) && !sendMap.get(dd).got)
                sendMap.get(dd).sendTime--;
        }
        sockMap.remove(channel);
        staOut++;
        channel.close();
        deadSock.add(num);
    }

    void dealTimeout() throws Exception {
        long tt = System.currentTimeMillis();
        ArrayList<DatagramChannel> temp = new ArrayList<DatagramChannel>(sockMap.keySet());
        for (DatagramChannel k : temp) {
            if (sockMap.get(k).createTime + timeoutTime < tt)
                addToDeadSock(k);
        }
    }

    public static void main(String[] args) throws Exception {
    }
}

class myThr extends Thread {
    cli a;

    public void run() {
        try {
            a.doAccept();
            while (true) {
                a.work();
            }
        } catch (Exception e) {
            helpFunc.print(e.toString() + e.getStackTrace()[0].toString());
            e.printStackTrace();
            a.myLog.setLog(e.toString());
        }
    }
}
